package com.googlecode.connectlet.machine.provider.cipher;

import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;

import javax.crypto.CipherSpi;

import com.googlecode.connectlet.machine.provider.MachineKey;
import com.googlecode.connectlet.machine.provider.MachineProvider;
import com.googlecode.connectlet.util.Bytes;

public abstract class MachineCipher extends CipherSpi {
	protected abstract void init(int opmode, Key key) throws InvalidKeyException;
	protected abstract byte[] doFinal(byte[] data);

	@Deprecated
	@Override
	protected void engineSetMode(String mode) {
		throw new UnsupportedOperationException();
	}

	@Deprecated
	@Override
	protected void engineSetPadding(String padding) {
		throw new UnsupportedOperationException();
	}

	@Deprecated
	@Override
	protected void engineInit(int opmode, Key key, SecureRandom random) {
		throw new UnsupportedOperationException();
	}

	protected MachineKey machineKey;
	protected byte[] block;
	protected int blockOffset;

	@Override
	protected AlgorithmParameters engineGetParameters() {
		try {
			AlgorithmParameters param = AlgorithmParameters.getInstance("Machine",
					MachineProvider.getInstance());
			param.init(machineKey);
			return param;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	protected void engineInit(int opmode, Key key, AlgorithmParameterSpec params,
			SecureRandom random) throws InvalidKeyException,
			InvalidAlgorithmParameterException {
		if (params instanceof MachineKey) {
			machineKey = (MachineKey) params;
		} else {
			throw new InvalidAlgorithmParameterException("MachineKey required");
		}
		init(opmode, key);
		blockOffset = 0;
	}

	@Override
	protected void engineInit(int opmode, Key key, AlgorithmParameters params,
			SecureRandom random) throws InvalidKeyException,
			InvalidAlgorithmParameterException {
		try {
			machineKey = params.getParameterSpec(MachineKey.class);
		} catch (InvalidParameterSpecException e) {
			throw new InvalidAlgorithmParameterException(e);
		}
		init(opmode, key);
		blockOffset = 0;
	}

	@Override
	protected byte[] engineGetIV() {
		return null;
	}
	@Override
	protected int engineGetBlockSize() {
		return block.length;
	}

	@Override
	protected int engineGetOutputSize(int arg0) {
		return block.length;
	}

	@Override
	protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
		System.arraycopy(input, inputOffset, block, blockOffset, inputLen);
		blockOffset += inputLen;
		return new byte[0];
	}

	@Override
	protected int engineUpdate(byte[] input, int inputOffset, int inputLen,
			byte[] output, int outputOffset) {
		System.arraycopy(input, inputOffset, block, blockOffset, inputLen);
		blockOffset += inputLen;
		return 0;
	}

	@Override
	protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) {
		engineUpdate(input, inputOffset, inputLen);
		byte[] result = doFinal(Bytes.left(block, blockOffset));
		blockOffset = 0;
		return result;
	}

	@Override
	protected int engineDoFinal(byte[] input, int inputOffset, int inputLen,
			byte[] output, int outputOffset) {
		byte[] result = engineDoFinal(input, inputOffset, inputLen);
		System.arraycopy(result, 0, output, outputOffset, result.length);
		return result.length;
	}
}